//
// StandardPomKnobParams
//
// Copyright (C) jextra.net.
//
//  This file is part of the net.jextra.crackdep Library.
//
//  The net.jextra.crackdep Library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The net.jextra.crackdep Library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the net.jextra.crackdep Library; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.crackdep;

import java.io.*;
import java.util.*;
import net.jextra.crack.*;
import net.jextra.crack.java.*;
import net.jextra.crack.props.*;
import net.jextra.crack.tool.*;
import net.jextra.crack.type.*;
import net.jextra.dep.*;
import net.jextra.dep.pom.*;
import net.jextra.dep.repo.*;

public class StandardPomKnobParams implements JavaKnobParams, PomKnobParams
{
    // ============================================================
    // Fields
    // ============================================================

    public static final String STYLE = "pom";

    public static final String DIR_ENV = "env";
    public static final String DIR_TARGET = "target";
    public static final String DIR_CLASSES = "classes";
    public static final String DIR_JAVADOC = "javadoc";
    public static final String DIR_BUILD = "build";
    public static final String DIR_SHRINKWRAP = "shrinkwrap";
    public static final String FILE_CRACK_PROPERTIES = "crack.properties";

    private DepConfReader confReader;
    private DepTree depTree;
    private DepTree teamDepTree;
    private DepDirFilter dirFilter;

    private File selfPomFile;
    private File projectRootDir;
    private String componentName;
    private String buildName;
    private String versionNumber;
    private HashSet<File> sourceDirs;
    private FileSet sourceFileSet;
    private HashSet<File> resourceDirs;
    private File targetDir;
    private File envDir;
    private File classesDir;
    private File javadocDir;
    private File rootBuildDir;
    private File rootShrinkwrapDir;

    // ============================================================
    // Constructors
    // ============================================================

    public StandardPomKnobParams()
    {
        try
        {
            //
            // Search for pom.xml file in order to find component root.
            //
            for ( File dir = new File( PomReader.POM_FILENAME ).getAbsoluteFile().getParentFile(); dir != null; dir = dir
                .getParentFile() )
            {
                File file = new File( dir, PomReader.POM_FILENAME );
                if ( file.exists() && file.isFile() )
                {
                    selfPomFile = file;
                    break;
                }
            }

            assert selfPomFile != null : "Unable to find " + PomReader.POM_FILENAME + " file in source path.";

            //
            // WARNING: It is ASSUMED that the project's root dir is always just the parent of the component root dir.
            //
            projectRootDir = getComponentRootDir().getParentFile();

            dirFilter = new StandardDirFilter( this );

            //
            // Parse pom.xml file.
            //
            confReader = new DepConfReader( STYLE );
            FileInputStream in = new FileInputStream( CrackBuild.getInstance().getCrackContext()
                .locateConfigFile( DepConfReader.FILENAME ) );
            confReader.parse( in );
            in.close();

            ArrayList<DepRepo> repos = new ArrayList<DepRepo>();
            repos.add( confReader.getLocalRepo() );

            PomReader reader = new PomReader();
            FileInputStream pomIn = new FileInputStream( selfPomFile );
            reader.read( pomIn );
            pomIn.close();
            DepComponent component = reader.getComponent();

            componentName = component.getName();
            int slash = componentName.lastIndexOf( '/' );
            buildName = componentName.substring( slash + 1 );

            versionNumber = component.getVersionNumber();
            assert versionNumber != null : "Version number must be specified in pom.xml file.";

            //
            // Setup output dirs.
            //
            envDir = new File( projectRootDir, DIR_ENV );
            targetDir = new File( projectRootDir, DIR_TARGET );
            javadocDir = new File( targetDir, DIR_JAVADOC );
            rootBuildDir = new File( targetDir, DIR_BUILD );
            rootShrinkwrapDir = new File( targetDir, DIR_SHRINKWRAP );

            //
            // Search for source dirs and fileset.
            //
            DirStrainer dirStrainer = new InternalDirStrainer();
            RegexFileStrainer javaStrainer = new RegexFileStrainer( dirStrainer ).include( ".*\\.java" ).include(
                ".*\\.crack" );
            sourceFileSet = new StrainedFileSet( getComponentRootDir(), javaStrainer );
            System.out.println( "getComponentRootDir " + getComponentRootDir().getAbsolutePath() );

            // Find all paths to .java files in component.
            sourceDirs = new LinkedHashSet<File>();
            JavaPackageFinder finder = new JavaPackageFinder();
            for ( File javaFile : javaStrainer.getFiles( getComponentRootDir() ) )
            {
                finder.read( javaFile );
                sourceDirs.add( finder.getRootDir() );
            }

            //
            // WARNING: It is ASSUMED that resourceDirs = all sourceDirs + any other dirs at same level as any
            // of the sourceDirs.
            //
            resourceDirs = new LinkedHashSet<File>( sourceDirs );
            for ( File sourceDir : sourceDirs )
            {
                for ( File dir : sourceDir.getParentFile().listFiles() )
                {
                    if ( !dir.isDirectory() )
                    {
                        continue;
                    }
                    
                    resourceDirs.add( dir );
                }
            }

            //
            // Override classesDir if specified in Props by now.
            //
            System.out.println( "envDir=" + getEnvDir() );
            File file = new File( getEnvDir(), "crack.properties" );
            if ( file.exists() )
            {
                Properties props = new Properties();
                FileInputStream propIn = new FileInputStream( file );
                props.load( propIn );
                propIn.close();
                CrackProps.addProperties( "env", props );
            }
            if ( CrackProps.getString( "classes.dir" ) != null )
            {
                classesDir = new File( projectRootDir, CrackProps.getString( "classes.dir" ) );
            }
            else
            {
                classesDir = new File( targetDir, DIR_CLASSES );
            }
        }
        catch ( Exception ex )
        {
            throw new RuntimeException( ex );
        }
    }

    // ============================================================
    // Methods
    // ============================================================
    
    @Override
    public String getBuildName()
    {
        return buildName;
    }

    @Override
    public String getComponentName()
    {
        return componentName;
    }

    @Override
    public String getVersionNumber()
    {
        return versionNumber;
    }

    @Override
    public String getClasspath()
        throws Exception
    {
        try
        {
            StringBuilder builder = new StringBuilder();
            for ( File file : getDependencies( "compile", "runtime" ) )
            {
                if ( builder.length() > 0 )
                {
                    builder.append( File.pathSeparator );
                }
                builder.append( file.getCanonicalPath() );
            }

            return builder.toString();
        }
        catch ( Exception ex )
        {
            throw new DepException( ex );
        }
    }

    @Override
    public FileRepo getLocalRepo()
        throws Exception
    {
        return (FileRepo) confReader.getLocalRepo();
    }

    public File getTargetDir()
    {
        return targetDir;
    }

    public File getProjectRootDir()
    {
        return projectRootDir;
    }

    public File getComponentRootDir()
    {
        return selfPomFile.getParentFile();
    }

    @Override
    public Collection<File> getSourceDirs()
    {
        return sourceDirs;
    }

    @Override
    public FileSet getSourceFileSet()
    {
        return sourceFileSet;
    }

    @Override
    public Collection<File> getResourceDirs()
    {
        return resourceDirs;
    }

    public void clearSourceDirs()
    {
        sourceDirs.clear();
    }

    public void addSourceDir( File sourceDir )
    {
        sourceDirs.add( sourceDir );
    }

    @Override
    public File getClassesDir()
    {
        if ( classesDir != null )
        {
            return classesDir;
        }

        //
        // Override classesDir if specified in Props by now.
        //
        if ( CrackProps.getString( "classes.dir" ) != null )
        {
            return new File( getComponentRootDir(), CrackProps.getString( "classes.dir" ) );
        }

        return new File( projectRootDir, DIR_CLASSES );
    }

    public void setClassesDir( File classesDir )
    {
        this.classesDir = classesDir;
    }

    @Override
    public File getJavadocDir()
    {
        return javadocDir;
    }

    public void setJavadocDir( File javadocDir )
    {
        this.javadocDir = javadocDir;
    }

    public File getEnvDir()
    {
        return envDir;
    }

    public void setEnvDir( File envDir )
    {
        this.envDir = envDir;
    }

    @Override
    public File getBuildDir()
    {
        File dir = new File( rootBuildDir, getBuildName() );
        dir = new File( dir, getVersionNumber() );

        return dir;
    }

    @Override
    public File getShrinkwrapDir()
        throws Exception
    {
        File dir = new File( rootShrinkwrapDir, getBuildName() );
        dir = new File( dir, getVersionNumber() );

        return dir;
    }

    public DepDirFilter getDirFilter()
    {
        return dirFilter;
    }

    @Override
    public Collection<File> getDependencies( String ... scopes )
    {
        try
        {
            LinkedHashSet<File> list = new LinkedHashSet<File>();

            DepTreeSearcher searcher = new DepTreeSearcher( getDepTree( scopes ) );
            for ( DepComponentFile entry : searcher.getFiles() )
            {
                list.add( getLocalRepo().getFile( entry.getComponent().getName(),
                    entry.getComponent().getVersionNumber(), entry.getName() ) );
            }

            return list;
        }
        catch ( Exception ex )
        {
            throw new DepException( ex );
        }
    }

    public Collection<File> getRuntimeDependencies()
    {
        return getDependencies( "compile", "runtime", "system" );
    }

    public Collection<File> getCompileDependencies()
    {
        return getDependencies( "compile", "provided", "test", "system" );
    }

    public DepTree getWorkingDepTree()
    {
        return getDepTree( "compile", "test", "runtime", "system" );
    }

    public DepTree getCompileDepTree()
    {
        return getDepTree( "compile", "provided", "test", "system" );
    }

    @Override
    public DepTree getDepTree( String ... scopes )
    {
        assert confReader.getLocalRepo() != null : "getLocalRepo can not be null.";

        try
        {
            if ( depTree == null )
            {
                ArrayList<DepRepo> repos = new ArrayList<DepRepo>();
                repos.add( confReader.getLocalRepo() );

                PomExplorer explorer = new PomExplorer( scopes );
                depTree = explorer.execute( projectRootDir, repos, dirFilter );
            }

            return depTree;
        }
        catch ( Exception ex )
        {
            throw new DepException( ex );
        }
    }

    @Override
    public DepRepo getTeamRepo( String name )
        throws Exception
    {
        return confReader.getTeamRepo( name );
    }

    @Override
    public DepTree getTeamDepTree()
    {
        assert confReader.getTeamRepos() != null : "getTeamRepos can not be null.";

        try
        {
            if ( teamDepTree == null )
            {
                PomExplorer explorer = new PomExplorer( "compile" );
                teamDepTree = explorer.execute( projectRootDir, confReader.getTeamRepos(), dirFilter );
            }

            return teamDepTree;
        }
        catch ( Exception ex )
        {
            throw new DepException( ex );
        }
    }

    @Override
    public File getSelfPomFile()
    {
        return selfPomFile;
    }

    // ============================================================
    // Inner Classes
    // ============================================================

    public static class StandardDirFilter implements DepDirFilter
    {
        private StandardPomKnobParams params;

        public StandardDirFilter( StandardPomKnobParams params )
        {
            this.params = params;
        }

        @Override
        public boolean isDepSearchDir( File dir )
            throws Exception
        {
            if ( dir.getName().startsWith( "." ) )
            {
                return false;
            }

            if ( dir.getCanonicalPath().equalsIgnoreCase( params.getEnvDir().getCanonicalPath() ) )
            {
                return false;
            }

            if ( dir.getCanonicalPath().equalsIgnoreCase( params.getTargetDir().getCanonicalPath() ) )
            {
                return false;
            }

            return true;
        }
    }

    private class InternalDirStrainer implements DirStrainer
    {
        @Override
        public Collection<File> getDirs( File rootDir )
        {
            ArrayList<File> dirs = new ArrayList<File>();

            for ( File dir : rootDir.listFiles() )
            {
                try
                {
                    dirs.addAll( getDirsRecurse( dir ) );
                }
                catch ( IOException ex )
                {
                    // Just ignore dir.
                }
            }

            return dirs;
        }

        private Collection<File> getDirsRecurse( File dir )
            throws IOException
        {
            Collection<File> dirs = new LinkedHashSet<File>();

            if ( !dir.isDirectory() )
            {
                return dirs;
            }

            if ( dir.getName().startsWith( "." ) )
            {
                return dirs;
            }

            if ( getEnvDir() != null && dir.getCanonicalPath().equalsIgnoreCase( getEnvDir().getCanonicalPath() ) )
            {
                return dirs;
            }

            if ( getTargetDir() != null && dir.getCanonicalPath().equalsIgnoreCase( getTargetDir().getCanonicalPath() ) )
            {
                return dirs;
            }

            // Add self
            dirs.add( dir );

            for ( File child : dir.listFiles() )
            {
                // Only return directories.
                if ( !child.isDirectory() )
                {
                    continue;
                }

                // Add children.
                dirs.addAll( getDirsRecurse( child ) );
            }

            return dirs;
        }

    }
}
